/**
 * 广寒宫
 * 网址:www.guanghangong.xyz
 */
package org.moon.framework.autoconfigure.utils;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.cglib.beans.BeanCopier;
import org.springframework.cglib.beans.BeanMap;
import org.springframework.lang.Nullable;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.Reader;
import java.nio.CharBuffer;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 高频方法集合类  只做简单的调用，不删除原有工具类
 * @author moon
 */
@Slf4j
public class Func {

	/**
	 * 字符串常量：冒号 {@code ":"}
	 */
	public static final String COLON = ":";
	/**
	 * 字符串常量：艾特 "@"
	 */
	public static final String AT = "@";
	/**
	 * 字符串常量：下划线 {@code "_"}
	 */
	public static final String UNDERLINE = "_";

	/**
	 * 字符串常量：减号（连接符） {@code "-"}
	 */
	public static final String DASHED = "-";

	/**
	 * 字符串常量：逗号 {@code ","}
	 */
	public static final String COMMA = ",";
	
	private static final Map<String, BeanCopier> BEAN_COPIER_MAP = new ConcurrentHashMap<>();
	
    /**
     * 获取请求对象
     */
	public static HttpServletRequest getRequest() {
		try {
			return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
		} catch (Exception e) {
			return null;
		}
	}

	/**
	 * 获得所有请求参数
	 *
	 * @param request 请求对象{@link ServletRequest}
	 * @return Map
	 */
	public static Map<String, String> getParamMap(ServletRequest request) {
		Map<String, String> params = new HashMap<>();
		Map<String, String[]> map = request.getParameterMap();
		for (Map.Entry<String, String[]> entry : map.entrySet()) {
			params.put(entry.getKey(), toArrayStr(entry.getValue(), COMMA));
		}
		return params;
	}

	/**
	 * 获取请求体<br>
	 * 调用该方法后，getParam方法将失效
	 * @param request {@link ServletRequest}
	 * @return 获得请求体
	 */
	public static String getBody(ServletRequest request) {
		try(final BufferedReader reader = request.getReader()) {
			return read(reader,true);
		} catch (IOException e) {
			log.error(e.getMessage(),e);
		}
		return null;
	}
	/**
	 * 获取 userAgent
	 */
	public static String getUserAgent() {
		HttpServletRequest request = getRequest();
		if(request==null){
			return null;
		}
		String ua = request.getHeader("User-Agent");
		return ua != null ? ua : "";
	}


	/**
	 * 从{@link Reader}中读取String
	 *
	 * @param reader  {@link Reader}
	 * @param isClose 是否关闭{@link Reader}
	 * @return String
	 */
	private static String read(Reader reader, boolean isClose) throws IOException {
		final StringBuilder builder = new StringBuilder();
		//默认缓存大小 8192
		final CharBuffer buffer = CharBuffer.allocate(2 << 12);
		try {
			while (-1 != reader.read(buffer)) {
				builder.append(buffer.flip().toString());
			}
		} catch (IOException e) {
			throw new IOException(e);
		} finally {
			if (isClose) {
				if (null != reader) {
					try {
						reader.close();
					} catch (Exception e) {
						// 静默关闭
					}
				}
			}
		}
		return builder.toString();
	}
	/**
	 * 获取大写的uuid,不包含-
	 */
    public static String getUpperUUID() {
        return UUID.randomUUID().toString().toUpperCase().replaceAll("-", "");
    }
    /**
     * 获取小写的uuid,不包含
     */
    public static String getLowerUUID() {
    	return UUID.randomUUID().toString().toLowerCase().replaceAll("-", "");
    }

	/**
	 * Determine whether the given object is empty:
	 * i.e. {@code null} or of zero length.
	 *
	 * @param obj the object to check
	 */
	public static boolean isEmpty(@Nullable Object obj) {
		return ObjectUtils.isEmpty(obj);
	}

	public static boolean isNotEmpty(@Nullable Object obj) {
		return !ObjectUtils.isEmpty(obj);
	}

	/**
	 * Check whether the given {@code CharSequence} contains actual <em>text</em>.
	 * <p>More specifically, this method returns {@code true} if the
	 * {@code CharSequence} is not {@code null}, its length is greater than
	 * 0, and it contains at least one non-whitespace character.
	 * <pre class="code">
	 * $.isBlank(null)		= true
	 * $.isBlank("")		= true
	 * $.isBlank(" ")		= true
	 * $.isBlank("12345")	= false
	 * $.isBlank(" 12345 ")	= false
	 * </pre>
	 *
	 * @param cs the {@code CharSequence} to check (may be {@code null})
	 * @return {@code true} if the {@code CharSequence} is not {@code null},
	 * its length is greater than 0, and it does not contain whitespace only
	 * @see Character#isWhitespace
	 */
	public static boolean isBlank(@Nullable final CharSequence cs) {
		return StringUtils.isBlank(cs);
	}

	public static boolean isNotBlank(@Nullable final CharSequence cs) {
		return StringUtils.isNotBlank(cs);
	}


	/**
	 * Determine whether the given object is an array:
	 * either an Object array or a primitive array.
	 *
	 * @param obj the object to check
	 * @return 是否数组
	 */
	public static boolean isArray(@Nullable Object obj) {
		return ObjectUtils.isArray(obj);
	}

	/**
	 * Check whether the given Array contains the given element.
	 *
	 * @param array   the Array to check
	 * @param element the element to look for
	 * @param <T>     The generic tag
	 * @return {@code true} if found, {@code false} else
	 */
	public static <T> boolean contains(@Nullable T[] array, final T element) {
		return Arrays.stream(array).anyMatch(x -> ObjectUtils.nullSafeEquals(x, element));
	}

	/**
	 * Check whether the given Iterator contains the given element.
	 *
	 * @param collection the Iterator to check
	 * @param element  the element to look for
	 * @return {@code true} if found, {@code false} otherwise
	 */
	public static boolean contains(@Nullable Collection<?> collection, Object element) {
		return CollectionUtils.contains(collection.iterator(), element);
	}

	/**
	 * 强转string,并去掉多余空格
	 *
	 * @param str 字符串
	 * @return String
	 */
	public static String toStr(Object str) {
		return toStr(str, "");
	}

	/**
	 * 强转string,并去掉多余空格
	 *
	 * @param str          字符串
	 * @param defaultValue 默认值
	 * @return String
	 */
	public static String toStr(Object str, String defaultValue) {
		if (null == str) {
			return defaultValue;
		}
		return String.valueOf(str);
	}

	/**
	 * 判断一个字符串是否是数字
	 *
	 * @param cs the CharSequence to check, may be null
	 * @return {boolean}
	 */
	public static boolean isNumeric(final CharSequence cs) {
		return StringUtils.isNumeric(cs);
	}

	/**
	 * <p>Convert a <code>String</code> to an <code>int</code>, returning
	 * <code>zero</code> if the conversion fails.</p>
	 *
	 * <p>If the string is <code>null</code>, <code>zero</code> is returned.</p>
	 *
	 * <pre>
	 *   $.toInt(null) = 0
	 *   $.toInt("")   = 0
	 *   $.toInt("1")  = 1
	 * </pre>
	 *
	 * @param value the string to convert, may be null
	 * @return the int represented by the string, or <code>zero</code> if
	 * conversion fails
	 */
	public static int toInt(final Object value) {
		return toInt(value,0);
	}

	/**
	 * <p>Convert a <code>String</code> to an <code>int</code>, returning a
	 * default value if the conversion fails.</p>
	 *
	 * <p>If the string is <code>null</code>, the default value is returned.</p>
	 *
	 * <pre>
	 *   $.toInt(null, 1) = 1
	 *   $.toInt("", 1)   = 1
	 *   $.toInt("1", 0)  = 1
	 * </pre>
	 *
	 * @param value        the string to convert, may be null
	 * @param defaultValue the default value
	 * @return the int represented by the string, or the default if conversion fails
	 */
	public static int toInt(final Object value, final int defaultValue) {
		String str = String.valueOf(value);
		if (str == null) {
			return defaultValue;
		}
		try {
			return Integer.valueOf(str);
		} catch (final NumberFormatException nfe) {
			return defaultValue;
		}
	}

	/**
	 * <p>Convert a <code>String</code> to a <code>long</code>, returning
	 * <code>zero</code> if the conversion fails.</p>
	 *
	 * @param value the string to convert, may be null
	 * @return the long represented by the string, or <code>0</code> if
	 * conversion fails
	 */
	public static long toLong(final Object value) {
		return toLong(value,0L);
	}

	/**
	 * <p>Convert a <code>String</code> to a <code>long</code>, returning a
	 * default value if the conversion fails.</p>
	 *
	 * <p>If the string is <code>null</code>, the default value is returned.</p>
	 *
	 * <pre>
	 *   $.toLong(null, 1L) = 1L
	 *   $.toLong("", 1L)   = 1L
	 *   $.toLong("1", 0L)  = 1L
	 * </pre>
	 *
	 * @param value        the string to convert, may be null
	 * @param defaultValue the default value
	 * @return the long represented by the string, or the default if conversion fails
	 */
	public static long toLong(final Object value, final long defaultValue) {
		String str = String.valueOf(value);
		if (str == null) {
			return defaultValue;
		}
		try {
			return Long.valueOf(str);
		} catch (final NumberFormatException nfe) {
			return defaultValue;
		}
	}

	/**
	 * <p>Convert a <code>String</code> to an <code>Double</code>returning
	 * <code>zero</code> if the conversion fails.</p>
	 *
	 * @param value the string to convert, may be null
	 * @return the int represented by the string, or the default if conversion fails
	 */
	public static double toDouble(Object value) {
		return toDouble(String.valueOf(value), 0.00d);
	}

	/**
	 * <p>Convert a <code>String</code> to an <code>Double</code>, returning a
	 * default value if the conversion fails.</p>
	 *
	 * <p>If the string is <code>null</code>, the default value is returned.</p>
	 *
	 * <pre>
	 *   $.toDouble(null, 1) = 1.0
	 *   $.toDouble("", 1)   = 1.0
	 *   $.toDouble("1", 0)  = 1.0
	 * </pre>
	 *
	 * @param value        the string to convert, may be null
	 * @param defaultValue the default value
	 * @return the int represented by the string, or the default if conversion fails
	 */
	public static double toDouble(Object value, Double defaultValue) {
		if (value != null) {
			return Double.valueOf(String.valueOf(value).trim());
		}
		return defaultValue;
	}

	/**
	 * <p>Convert a <code>String</code> to an <code>Float</code>returning
	 * <code>zero</code> if the conversion fails.</p>
	 *
	 * @param value the string to convert, may be null
	 * @return the int represented by the string, or the default if conversion fails
	 */
	public static float toFloat(Object value) {
		return toFloat(String.valueOf(value), 0.0f);
	}

	/**
	 * <p>Convert a <code>String</code> to an <code>Float</code>, returning a
	 * default value if the conversion fails.</p>
	 *
	 * <p>If the string is <code>null</code>, the default value is returned.</p>
	 *
	 * <pre>
	 *   $.toFloat(null, 1) = 1.00f
	 *   $.toFloat("", 1)   = 1.00f
	 *   $.toFloat("1", 0)  = 1.00f
	 * </pre>
	 *
	 * @param value        the string to convert, may be null
	 * @param defaultValue the default value
	 * @return the int represented by the string, or the default if conversion fails
	 */
	public static float toFloat(Object value, Float defaultValue) {
		if (value != null) {
			return Float.valueOf(String.valueOf(value).trim());
		}
		return defaultValue;
	}

	/**
	 * <p>Convert a <code>String</code> to an <code>Boolean</code>returning
	 * <code>false</code> if the conversion fails.</p>
	 * 
	 * @param value the string to convert, may be null
	 * @return the int represented by the string, or the default if conversion fails
	 */
	public static boolean toBoolean(Object value) {
		return toBoolean(value, false);
	}

	/**
	 * <p>Convert a <code>String</code> to an <code>Boolean</code>, returning a
	 * default value if the conversion fails.</p>
	 *
	 * <p>If the string is <code>null</code>, the default value is returned.</p>
	 *
	 * <pre>
	 *   $.toBoolean("true", true)  = true
	 *   $.toBoolean("false")   	= false
	 *   $.toBoolean("", false)  	= false
	 * </pre>
	 *
	 * @param value        the string to convert, may be null
	 * @param defaultValue the default value
	 * @return the int represented by the string, or the default if conversion fails
	 */
	public static boolean toBoolean(Object value, Boolean defaultValue) {
		if (value != null) {
			String val = String.valueOf(value);
			val = val.toLowerCase().trim();
			return Boolean.parseBoolean(val);
		}
		return defaultValue;
	}

	/**
	 * 转换为Integer数组<br>
	 *
	 * @param str 被转换的值
	 * @return 结果
	 */
	public static Integer[] toIntArray(String str) {
		return toIntArray(",", str);
	}

	/**
	 * 转换为Integer数组<br>
	 *
	 * @param split 分隔符
	 * @param str   被转换的值
	 * @return 结果
	 */
	public static Integer[] toIntArray(String split, String str) {
		if (StringUtils.isEmpty(str)) {
			return new Integer[]{};
		}
		String[] arr = str.split(split);
		final Integer[] ints = new Integer[arr.length];
		for (int i = 0; i < arr.length; i++) {
			final Integer v = toInt(arr[i], 0);
			ints[i] = v;
		}
		return ints;
	}

	/**
	 * 转换为Integer集合<br>
	 *
	 * @param str 结果被转换的值
	 * @return 结果
	 */
	public static List<Integer> toIntList(String str) {
		return Arrays.asList(toIntArray(str));
	}

	/**
	 * 转换为Integer集合<br>
	 *
	 * @param split 分隔符
	 * @param str   被转换的值
	 * @return 结果
	 */
	public static List<Integer> toIntList(String split, String str) {
		return Arrays.asList(toIntArray(split, str));
	}

	/**
	 * 转换为Long数组<br>
	 *
	 * @param str 被转换的值
	 * @return 结果
	 */
	public static Long[] toLongArray(String str) {
		return toLongArray(",", str);
	}

	/**
	 * 转换为Long数组<br>
	 *
	 * @param split 分隔符
	 * @param str   被转换的值
	 * @return 结果
	 */
	public static Long[] toLongArray(String split, String str) {
		if (StringUtils.isEmpty(str)) {
			return new Long[]{};
		}
		String[] arr = str.split(split);
		final Long[] longs = new Long[arr.length];
		for (int i = 0; i < arr.length; i++) {
			final Long v = toLong(arr[i], 0);
			longs[i] = v;
		}
		return longs;
	}

	/**
	 * 转换为Long集合<br>
	 *
	 * @param str 结果被转换的值
	 * @return 结果
	 */
	public static List<Long> toLongList(String str) {
		return Arrays.asList(toLongArray(str));
	}

	/**
	 * 转换为Long集合<br>
	 *
	 * @param split 分隔符
	 * @param str   被转换的值
	 * @return 结果
	 */
	public static List<Long> toLongList(String split, String str) {
		return Arrays.asList(toLongArray(split, str));
	}

	/**
	 * String数组转换为String<br>
	 */
	public static String toArrayStr(String [] arrays,String split){
		split = split==null?COMMA:split;
		if(arrays==null || arrays.length==0)return StringUtils.EMPTY;
		StringBuffer sb = new StringBuffer();
		for(int i=0;i<arrays.length;i++){
			sb.append(arrays[i]);
			if(i<arrays.length-1){
				sb.append(split);
			}
		}
		return sb.toString();
	}

	/**
	 * String数组转换为String<br>
	 */
	public static String toArrayStr(String [] arrays){
		return toArrayStr(arrays,COMMA);
	}
	/**
	 * 转换为String数组<br>
	 *
	 * @param str 被转换的值
	 * @return 结果
	 */
	public static String[] toStrArray(String str) {
		return toStrArray(",", str);
	}

	/**
	 * 转换为String数组<br>
	 *
	 * @param split 分隔符
	 * @param str   被转换的值
	 * @return 结果
	 */
	public static String[] toStrArray(String split, String str) {
		if (isBlank(str)) {
			return new String[]{};
		}
		return str.split(split);
	}

	/**
	 * 转换为String集合<br>
	 *
	 * @param str 结果被转换的值
	 * @return 结果
	 */
	public static List<String> toStrList(String str) {
		return Arrays.asList(toStrArray(str));
	}

	/**
	 * 转换为String集合<br>
	 *
	 * @param split 分隔符
	 * @param str   被转换的值
	 * @return 结果
	 */
	public static List<String> toStrList(String split, String str) {
		return Arrays.asList(toStrArray(split, str));
	}
	
	/**
	 * 从指写字符串里随机获取指定长度的字符串
	 */
	public static String getRandom(String src,int length){
        Random random = new Random();
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < length; i++) {
            int number = random.nextInt(src.length());
            sb.append(src.charAt(number));
        }
        return sb.toString();
	}
	
    /**
     * 获取随机位数的字符串
     */
    public static String getRandomString(int length) {
       return getRandom("abcdefghijklmnopqrstuvwxyz0123456789",length);
    }
    
    /**
     * 获取随机位数的数字
     *
     */
    public static String getRandomNumber(int length) {
    	return getRandom("0123456789",length);
    }

	/**
	 * 将java bean转成 map
	 */
	public static Map<String, Object> toMap(Object bean){
		Map<String, Object> map = new HashMap<>();
		BeanMap beanMap = BeanMap.create(bean);
		for (Object object : beanMap.entrySet()) {
			if (object instanceof Map.Entry) {
				Map.Entry<String , Object> entry = (Map.Entry<String, Object>)object ;
				String key = entry.getKey();
				map.put(key, beanMap.get(key));
			}
		}
		return map;
	}
    /**
     * 判断是否是windows操作系统
     */
    public static Boolean isWinOs() {
        String os = System.getProperty("os.name");
        if (os.toLowerCase().startsWith("win")) {
            return true;
        } else {
            return false;
        }
    }
    
    /**
     * 属性复制
     */
    public static <T> T copy(Object source, Class<T> clazz) {
        T target;
        try {
        	target = clazz.newInstance();
        } catch (ReflectiveOperationException e) {
            throw new RuntimeException("fail to create instance of type" + clazz.getCanonicalName(), e);
        }
        Objects.requireNonNull(source, "source must not be null");
        Objects.requireNonNull(target, "target must not be null");
        String key = source.getClass().getCanonicalName().concat(target.getClass().getCanonicalName());
        BeanCopier beanCopier = BEAN_COPIER_MAP.computeIfAbsent(key, x -> BeanCopier.create(source.getClass(), target.getClass(), false));
        beanCopier.copy(source, target, null);
        return target;
    }
    
    /**
     * 复制列表
     * @param list   被复制列表
     * @param classz 复制类型
     * @return
     */
    public static <T> List<T> copyList(List<?> list, Class<T> classz) {
        List<T> resultList = new LinkedList<>();
        if (CollectionUtils.isEmpty(list)) {
            return resultList;
        }
        for (Object obj1 : list) {
            resultList.add(copy(obj1, classz));
        }
        return resultList;
    }

	/**
	 * 将驼峰式命名的字符串转换为下划线方式。如果转换前的驼峰式命名的字符串为空，则返回空字符串。<br>
	 * 例如：
	 *
	 * <pre>
	 * HelloWorld=》hello_world
	 * Hello_World=》hello_world
	 * HelloWorld_test=》hello_world_test
	 * </pre>
	 *
	 * @param str 转换前的驼峰式命名的字符串，也可以为下划线形式
	 * @return 转换后下划线方式命名的字符串
	 */
	public static String toUnderlineCase(String str) {
		char symbol = '_';
		if (str == null) {
			return null;
		}
		final int length = str.length();
		final StringBuilder sb = new StringBuilder();
		char c;
		for (int i = 0; i < length; i++) {
			c = str.charAt(i);
			if (Character.isUpperCase(c)) {
				final Character preChar = (i > 0) ? str.charAt(i - 1) : null;
				final Character nextChar = (i < str.length() - 1) ? str.charAt(i + 1) : null;

				if (null != preChar) {
					if (symbol == preChar) {
						// 前一个为分隔符
						if (null == nextChar || Character.isLowerCase(nextChar)) {
							//普通首字母大写，如_Abb -> _abb
							c = Character.toLowerCase(c);
						}
						//后一个为大写，按照专有名词对待，如_AB -> _AB
					} else if (Character.isLowerCase(preChar)) {
						// 前一个为小写
						sb.append(symbol);
						if (null == nextChar || Character.isLowerCase(nextChar)) {
							//普通首字母大写，如aBcc -> a_bcc
							c = Character.toLowerCase(c);
						}
						// 后一个为大写，按照专有名词对待，如aBC -> a_BC
					} else {
						//前一个为大写
						if (null == nextChar || Character.isLowerCase(nextChar)) {
							// 普通首字母大写，如ABcc -> A_bcc
							sb.append(symbol);
							c = Character.toLowerCase(c);
						}
						// 后一个为大写，按照专有名词对待，如ABC -> ABC
					}
				} else {
					// 首字母，需要根据后一个判断是否转为小写
					if (null == nextChar || Character.isLowerCase(nextChar)) {
						// 普通首字母大写，如Abc -> abc
						c = Character.toLowerCase(c);
					}
					// 后一个为大写，按照专有名词对待，如ABC -> ABC
				}
			}
			sb.append(c);
		}
		return sb.toString();
	}

	/**
	 * 将下划线方式命名的字符串转换为驼峰式。如果转换前的下划线大写方式命名的字符串为空，则返回空字符串。<br>
	 * 例如：hello_world=》helloWorld
	 *
	 * @param str 转换前的下划线大写方式命名的字符串
	 * @return 转换后的驼峰式命名的字符串
	 */
	public static String toCamelCase(String str) {
		if (str == null) {
			return null;
		}
		str = str.toLowerCase();
		StringBuilder sb = new StringBuilder(str.length());
		boolean upperCase = false;
		for (int i = 0; i < str.length(); i++) {
			char c = str.charAt(i);
			if (c == '_') {
				upperCase = true;
			} else if (upperCase) {
				sb.append(Character.toUpperCase(c));
				upperCase = false;
			} else {
				sb.append(c);
			}
		}
		return sb.toString();
	}

	public static String getFileExtension(String fullName) {
		if (isBlank(fullName)) {
			return "";
		} else {
			String fileName = (new File(fullName)).getName();
			int dotIndex = fileName.lastIndexOf(46);
			return dotIndex == -1 ? "" : fileName.substring(dotIndex + 1);
		}
	}
}